package net.jonbuck.tassoo.ui.view.page;

import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.List;

import net.jonbuck.tassoo.eventmanager.EventManager;
import net.jonbuck.tassoo.eventmanager.EventManagerImpl;
import net.jonbuck.tassoo.eventmanager.events.AttachmentTypesUpdatedEvent;
import net.jonbuck.tassoo.eventmanager.listener.AttachmentTypesUpdateListener;
import net.jonbuck.tassoo.model.Attachment;
import net.jonbuck.tassoo.model.AttachmentType;
import net.jonbuck.tassoo.model.Task;
import net.jonbuck.tassoo.model.TassooFactory;
import net.jonbuck.tassoo.model.TassooPackage;
import net.jonbuck.tassoo.model.provider.TassooItemProviderAdapterFactory;
import net.jonbuck.tassoo.model.util.TassooAdapterFactory;
import net.jonbuck.tassoo.persistence.dao.TassooDao;
import net.jonbuck.tassoo.persistence.dao.TassooDaoImpl;
import net.jonbuck.tassoo.ui.Activator;
import net.jonbuck.tassoo.ui.dialog.AttachmentDialog;
import net.jonbuck.tassoo.ui.dialog.LaunchApplicationDialog;
import net.jonbuck.tassoo.ui.nl.Messages;
import net.jonbuck.tassoo.ui.util.CommonImages;
import net.jonbuck.tassoo.ui.view.comparator.AttachmentsTableComparator;

import org.apache.commons.lang.StringUtils;
import org.apache.regexp.RE;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Platform;
import org.eclipse.core.runtime.Status;
import org.eclipse.core.runtime.preferences.ConfigurationScope;
import org.eclipse.emf.ecore.change.util.ChangeRecorder;
import org.eclipse.emf.edit.command.AddCommand;
import org.eclipse.emf.edit.command.RemoveCommand;
import org.eclipse.emf.edit.domain.AdapterFactoryEditingDomain;
import org.eclipse.emf.edit.ui.provider.AdapterFactoryContentProvider;
import org.eclipse.emf.edit.ui.provider.AdapterFactoryLabelProvider;
import org.eclipse.jface.action.Action;
import org.eclipse.jface.action.IAction;
import org.eclipse.jface.dialogs.IDialogConstants;
import org.eclipse.jface.dialogs.MessageDialog;
import org.eclipse.jface.resource.ImageDescriptor;
import org.eclipse.jface.viewers.ISelectionChangedListener;
import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.jface.viewers.SelectionChangedEvent;
import org.eclipse.jface.viewers.TableViewer;
import org.eclipse.swt.SWT;
import org.eclipse.swt.custom.CTabFolder;
import org.eclipse.swt.events.ControlAdapter;
import org.eclipse.swt.events.ControlEvent;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.graphics.Rectangle;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Event;
import org.eclipse.swt.widgets.Listener;
import org.eclipse.swt.widgets.Table;
import org.eclipse.swt.widgets.TableColumn;
import org.eclipse.ui.forms.widgets.Form;
import org.eclipse.ui.forms.widgets.FormToolkit;
import org.eclipse.ui.statushandlers.StatusManager;
import org.osgi.service.prefs.BackingStoreException;
import org.osgi.service.prefs.Preferences;

/**
 * 
 * 
 * @since 1.0.0
 */
public class TaskAttachmentsTab extends Composite implements AttachmentTypesUpdateListener {

	/**
	 * <p>
	 * <b>Selection adapter for the columns within the attachments table.</b><br/>
	 * The adapter is responsible for maintaining the direction, position and
	 * table column that it is associated with then when clicked manages the
	 * process of changing direction, setting up the comparator and invoking a
	 * refresh so that the table is sorted in line with the data held within the
	 * selected column.
	 * </p>
	 * 
	 * @since 1.0.0
	 */
	private class AttachmentColumnSelectionAdapter extends SelectionAdapter {

		/** The direction the data should be sorted. */
		private int direction = SWT.None;

		/** The position of the column within the table. */
		private int position;

		/** The <code>TableColumn</code> that this adapter is responsible for. */
		private TableColumn tableColumn;

		/**
		 * <p>
		 * <b>Default Constructor.</b><br/>
		 * Creates a new adapter setting the table column and position in the
		 * table.
		 * </p>
		 * 
		 * @param tableColumn
		 *            the column for which the selection adapter is being
		 *            created
		 * @param position
		 *            the position of the column in the view
		 */
		public AttachmentColumnSelectionAdapter(TableColumn tableColumn, int position) {
			super();
			this.tableColumn = tableColumn;
			this.position = position;
		}

		/**
		 * <p>
		 * <b>Sets the direction in which to sort the data.</b>
		 * </p>
		 * 
		 * @param direction
		 *            the direction in which to sort the data
		 */
		public void setDirection(int direction) {
			this.direction = direction;
		}

		/**
		 * <p>
		 * <b>Method is fired when the column header for which this adapter is
		 * associated is selected (clicked on).</b>
		 * </p>
		 */
		public void widgetSelected(SelectionEvent e) {
			// Reset the sort direction if the currently set sort column within
			// the attachments table is not the column for which this adpater is
			// responsible.
			if (attachmentsTable.getSortColumn() != this.tableColumn) {
				direction = SWT.None;
			}
			switch (direction) {
			case SWT.UP:
				direction = SWT.DOWN;
				break;
			case SWT.DOWN:
				direction = SWT.None;
				break;
			default:
				direction = SWT.UP;
			}
			attachmentsTableViewerComparator.setColumn(this.position);
			attachmentsTableViewerComparator.setDirection(direction);
			attachmentsTable.setSortDirection(direction);
			attachmentsTable.setSortColumn(tableColumn);
			attachmentsTableViewer.refresh();
			tassooNode.put(ATTACHMENTS_SECTION_SORT_COLUMN, tableColumn.getText());
			tassooNode.putInt(ATTACHMENTS_SECTION_SORT_DIRECTION, direction);
			try {
				tassooNode.flush();
			} catch (BackingStoreException e1) {
				// TODO Handle exception correctly
				e1.printStackTrace();
			}
		}

	}

	/** Constant used to persist the column that is sorting the data. */
	private static final String ATTACHMENTS_SECTION_SORT_COLUMN = "net.jonbuck.tassoo.ui.properties.tabbed.TaskAttachmentsSection.sort.column"; //$NON-NLS-1$

	/** Constant used to persist the sort direction for the table. */
	private static final String ATTACHMENTS_SECTION_SORT_DIRECTION = "net.jonbuck.tassoo.ui.properties.tabbed.TaskAttachmentsSection.sort.direction"; //$NON-NLS-1$;

	/** Constant used to define a default width for the table columns. */
	private static final int DEFAULT_WIDTH = 150;

	/** Adpater factory used to create new objects. */
	private TassooAdapterFactory adapterFactory;

	/**
	 * <p>
	 * <b>Action used to add a new attachment to the current task.</b>
	 * </p>
	 */
	private IAction addAttachmentAction = new Action(Messages.taskattachmentsection_action_addattachment_text) {

		public ImageDescriptor getDisabledImageDescriptor() {
			return CommonImages.PLUS_DISABLED;
		}

		public ImageDescriptor getImageDescriptor() {
			return CommonImages.PLUS;
		}

		public void run() {
			AttachmentDialog attachmentDialog = new AttachmentDialog(Display.getCurrent().getActiveShell(),
					TassooFactory.eINSTANCE.createAttachment(), Messages.attachmentdialog_title, Messages.attachmentdialog_message);
			if (attachmentDialog.open() == IDialogConstants.OK_ID) {
				Attachment newAttachment = attachmentDialog.getAttachment();
				if (task.getAttachmentContainer() == null) {
					task.setAttachmentContainer(TassooFactory.eINSTANCE.createAttachmentContainer());
				}
				editingDomain.getCommandStack().flush();
				editingDomain.getCommandStack().execute(
						AddCommand.create(editingDomain, task.getAttachmentContainer(),
								TassooPackage.Literals.ATTACHMENT_CONTAINER__ATTACHMENTS, newAttachment));
				populateAttachmentsTable();
			}
		}
	};

	/** The eclipse <code>Form</code> object on which the UI is built. */
	private Form attachmentsForm;

	/** The <code>Table</code> displays the attachments. */
	private Table attachmentsTable;

	/**
	 * 
	 */
	private ISelectionChangedListener attachmentsTableSelectionListener = new ISelectionChangedListener() {
		public void selectionChanged(SelectionChangedEvent event) {
			IStructuredSelection selection = (IStructuredSelection) event.getSelection();
			if (selection.getFirstElement() != null && selection.getFirstElement() instanceof Attachment) {
				selectedAttachment = (Attachment) selection.getFirstElement();
				editAttachmentAction.setEnabled(true);
				deleteAttachmentAction.setEnabled(true);
				launchAttachmentAction.setEnabled(true);
			} else {
				editAttachmentAction.setEnabled(true);
				deleteAttachmentAction.setEnabled(true);
				launchAttachmentAction.setEnabled(true);
			}
		}
	};

	/** The <code>TableViewer</code> is responsible for managing the data */
	private TableViewer attachmentsTableViewer;

	/** The <code>Comparator</code> that manages sorting the table data. */
	private AttachmentsTableComparator attachmentsTableViewerComparator = new AttachmentsTableComparator();

	/** ... TODO Javadoc ... */
	private IAction deleteAttachmentAction = new Action(Messages.taskattachmentsection_action_deleteattachment_text) {

		public ImageDescriptor getDisabledImageDescriptor() {
			return CommonImages.DELETE_DISABLED;
		}

		public ImageDescriptor getImageDescriptor() {
			return CommonImages.DELETE;
		}

		public void run() {
			if (MessageDialog.openQuestion(Display.getCurrent().getActiveShell(),
					Messages.bind(Messages.delete_question, Messages.referencetype_attachment),
					Messages.bind(Messages.delete_message, Messages.referencetype_attachment, selectedAttachment.getLabel()))) {
				editingDomain.getCommandStack().flush();
				editingDomain.getCommandStack().execute(
						RemoveCommand.create(editingDomain, task.getAttachmentContainer(),
								TassooPackage.Literals.ATTACHMENT_CONTAINER__ATTACHMENTS, selectedAttachment));
			}
		}
	};

	/**
	 * <p>
	 * <b>Action used to edit the current selected attachment.</b
	 * </p>
	 */
	private IAction editAttachmentAction = new Action(Messages.taskattachmentsection_action_editattachment_text) {
		public ImageDescriptor getDisabledImageDescriptor() {
			return CommonImages.EDIT_DISABLED;
		}

		public ImageDescriptor getImageDescriptor() {
			return CommonImages.EDIT;
		}

		public void run() {
			ChangeRecorder editRecorder = new ChangeRecorder(selectedAttachment);
			AttachmentDialog attachmentDialog = new AttachmentDialog(Display.getCurrent().getActiveShell(), selectedAttachment,
					Messages.attachmentdialog_edit_title, Messages.attachmentdialog_edit_message);
			if (attachmentDialog.open() == IDialogConstants.OK_ID) {
				editRecorder.endRecording();
			} else {
				editRecorder.endRecording().applyAndReverse();
			}
		}
	};

	/** The EMF <code>EditingDomain</em> used to perform actions against. */
	private AdapterFactoryEditingDomain editingDomain;

	/** ... TODO Javadoc ... */
	private EventManager eventManager = EventManagerImpl.getInstance();

	/** Internal instance for the <em>Label</em> column. */
	private TableColumn labelColumn;

	/**
	 * <p>
	 * <b>Action for launching the currently selected attachment.</b>
	 * </p>
	 */
	private IAction launchAttachmentAction = new Action(Messages.taskattachmentsection_action_launchattachment_text) {

		public ImageDescriptor getDisabledImageDescriptor() {
			return CommonImages.PROCESS_DISABLED;
		}

		public ImageDescriptor getImageDescriptor() {
			return CommonImages.PROCESS;
		}

		/**
		 * <p>
		 * <b>Launches the selected attachment.</b><br/>
		 * For the selected attachment, calculate which attachment type(s) match
		 * the selected attachment and then if there is more than one prompt the
		 * user for which application to use, once selected then launch the
		 * attachment using the selected program.
		 * </p>
		 */
		public void run() {
			AttachmentType chosenAttachmentType = null;
			ArrayList<AttachmentType> launchApps = new ArrayList<AttachmentType>();
			for (AttachmentType type : tassooDao.getAttachmentTypes().getAttachmentType()) {
				if (type.getType().equals(selectedAttachment.getType()) && type.getSelectionExpression() != null) {
					if (new RE(type.getSelectionExpression()).match(selectedAttachment.getLocation())) {
						launchApps.add(type);
					}
				}
			}

			if (launchApps.size() > 1) {
				// Prompt user for which application to use and then use the
				// selected application....
				LaunchApplicationDialog launchApplicationDialog = new LaunchApplicationDialog(
						Display.getCurrent().getActiveShell(), Messages.launchApplicationDialog_title,
						Messages.launchApplicationDialog_message, launchApps);
				if (launchApplicationDialog.open() == IDialogConstants.OK_ID) {
					for (AttachmentType type : launchApps) {
						if (type.getName().equals(launchApplicationDialog.getSelectedAttachmentType())) {
							chosenAttachmentType = type;
							break;
						}
					}
				} else {
					return;
				}
			} else if (launchApps.size() == 1) {
				// use the single application to launch the application..
				chosenAttachmentType = launchApps.get(0);
			} else {
				// No Applications found so warn user...
				String message = "There are no applications defined that are capable of launching the selected attachment, please check the configuration and try again.";
				StatusManager.getManager().handle(new Status(IStatus.WARNING, Activator.PLUGIN_ID, message), StatusManager.SHOW);
				return;
			}

			try {
				List<String> arguments = new ArrayList<String>();
				arguments.add(chosenAttachmentType.getCommand());
				for (String argument : StringUtils.split(chosenAttachmentType.getArguments(), ' ')) {
					if (StringUtils.contains(argument, '{') && StringUtils.contains(argument, '}')) {
						arguments.add(MessageFormat.format(argument, selectedAttachment.getLocation()));
					} else {
						arguments.add(argument);
					}
				}

				ProcessBuilder pb = new ProcessBuilder(arguments);
				pb.start();
			} catch (Exception e) {
				String message = "An error occurred trying to launch the selected application";
				StatusManager.getManager().handle(new Status(IStatus.ERROR, Activator.PLUGIN_ID, message, e), StatusManager.SHOW);
			}
		}
	};

	/** ... TODO Javadoc ... */
	private int position;

	/** ... TODO Javadoc ... */
	protected Attachment selectedAttachment;

	/** The <code>Composite</code> within which the attachments table is held. */
	private Composite tableContainer;

	/** The <code>Task</code> that is currently being processed. */
	private Task task;

	/** The <em>DAO</em> used to retrieve data from the task store. */
	private TassooDao tassooDao = TassooDaoImpl.getInstance();

	/** The preference node used to persist data for the application. */
	private Preferences tassooNode;

	/** Internal instance for the <em>Type</em> column. */
	private TableColumn typeColumn;

	/** Internal instance for the <em>Value</em> column. */
	private TableColumn valueColumn;

	/**
	 * 
	 * @param parent
	 * @param form
	 * @param position
	 */
	TaskAttachmentsTab(Composite parent, Form form, int position) {
		super(parent, SWT.NONE);
		this.attachmentsForm = form;
		this.position = position;
		init();
	}

	/**
	 * 
	 * @param taskAttachmentsTab
	 */
	private void createControl(Composite parent) {
		tableContainer = new Composite(parent, SWT.NONE);
		tableContainer.setLayout(new GridLayout());
		tableContainer.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));

		attachmentsTable = new Table(tableContainer, SWT.BORDER | SWT.FULL_SELECTION);
		attachmentsTable.setLayout(new GridLayout());
		attachmentsTable.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
		attachmentsTable.setHeaderVisible(true);
		attachmentsTable.setSortDirection(tassooNode.getInt(ATTACHMENTS_SECTION_SORT_DIRECTION, 0));
		attachmentsTable.addListener(SWT.MeasureItem, new Listener() {
			public void handleEvent(Event event) {
				event.height = 20;
			}
		});
		attachmentsTable.addControlListener(new ControlAdapter() {
			public void controlResized(ControlEvent e) {
				Rectangle attachmentsTableArea = attachmentsTable.getClientArea();
				float tableWidth = attachmentsTableArea.width - attachmentsTable.getVerticalBar().getSize().x;
				float shortWidth = Math.max(DEFAULT_WIDTH, (tableWidth / 100) * 20);
				float longWidth = Math.max(DEFAULT_WIDTH, (tableWidth / 100) * 60);
				typeColumn.setWidth((int) shortWidth);
				labelColumn.setWidth((int) shortWidth);
				valueColumn.setWidth((int) longWidth);
			}
		});

		typeColumn = new TableColumn(attachmentsTable, SWT.NONE);
		typeColumn.setText("Type");
		typeColumn.setWidth(DEFAULT_WIDTH);
		AttachmentColumnSelectionAdapter typeAdapter = new AttachmentColumnSelectionAdapter(typeColumn, 0);
		typeColumn.addSelectionListener(typeAdapter);
		if (tassooNode.get(ATTACHMENTS_SECTION_SORT_COLUMN, StringUtils.EMPTY).equals(typeColumn.getText())) {
			attachmentsTable.setSortColumn(typeColumn);
			attachmentsTableViewerComparator.setColumn(0);
			typeAdapter.setDirection(tassooNode.getInt(ATTACHMENTS_SECTION_SORT_DIRECTION, 0));
		}

		labelColumn = new TableColumn(attachmentsTable, SWT.NONE);
		labelColumn.setText("Description");
		labelColumn.setWidth(DEFAULT_WIDTH);
		AttachmentColumnSelectionAdapter labelAdapter = new AttachmentColumnSelectionAdapter(labelColumn, 1);
		labelColumn.addSelectionListener(labelAdapter);
		if (tassooNode.get(ATTACHMENTS_SECTION_SORT_COLUMN, StringUtils.EMPTY).equals(labelColumn.getText())) {
			attachmentsTable.setSortColumn(labelColumn);
			attachmentsTableViewerComparator.setColumn(1);
			labelAdapter.setDirection(tassooNode.getInt(ATTACHMENTS_SECTION_SORT_DIRECTION, 0));
		}

		valueColumn = new TableColumn(attachmentsTable, SWT.NONE);
		valueColumn.setText("Value");
		valueColumn.setWidth(DEFAULT_WIDTH);
		AttachmentColumnSelectionAdapter valueAdapter = new AttachmentColumnSelectionAdapter(valueColumn, 2);
		valueColumn.addSelectionListener(valueAdapter);
		if (tassooNode.get(ATTACHMENTS_SECTION_SORT_COLUMN, StringUtils.EMPTY).equals(valueColumn.getText())) {
			attachmentsTable.setSortColumn(valueColumn);
			attachmentsTableViewerComparator.setColumn(2);
			valueAdapter.setDirection(tassooNode.getInt(ATTACHMENTS_SECTION_SORT_DIRECTION, 0));
		}

		attachmentsTableViewer = new TableViewer(attachmentsTable);
		attachmentsTableViewer.setContentProvider(new AdapterFactoryContentProvider(adapterFactory));
		attachmentsTableViewer.setLabelProvider(new AdapterFactoryLabelProvider(adapterFactory));
		attachmentsTableViewer.setComparator(attachmentsTableViewerComparator);
		attachmentsTableViewer.addSelectionChangedListener(attachmentsTableSelectionListener);

		attachmentsTableViewerComparator.setDirection(attachmentsTable.getSortDirection());

	}

	/**
	 * 
	 */
	private void init() {
		CTabFolder parentFolder = (CTabFolder) this.getParent();
		parentFolder.addSelectionListener(new SelectionAdapter() {
			public void widgetSelected(SelectionEvent e) {
				CTabFolder folder = (CTabFolder) e.widget;
				if (folder.getSelectionIndex() == position) {
					FormToolkit toolkit = new FormToolkit(getDisplay());
					attachmentsForm.getToolBarManager().removeAll();

					editAttachmentAction.setEnabled(false);
					deleteAttachmentAction.setEnabled(false);
					launchAttachmentAction.setEnabled(false);

					attachmentsForm.getToolBarManager().add(addAttachmentAction);
					attachmentsForm.getToolBarManager().add(editAttachmentAction);
					attachmentsForm.getToolBarManager().add(deleteAttachmentAction);
					attachmentsForm.getToolBarManager().add(launchAttachmentAction);
					
					attachmentsForm.updateToolBar();
					toolkit.decorateFormHeading(attachmentsForm);
					toolkit.dispose();
				}
			}
		});

		GridLayout gl = new GridLayout();
		gl.horizontalSpacing = 0;
		gl.verticalSpacing = 0;
		gl.marginWidth = 0;
		gl.marginHeight = 0;
		setLayout(gl);

		adapterFactory = new TassooItemProviderAdapterFactory();
		editingDomain = tassooDao.getEditingDomain();
		tassooNode = Platform.getPreferencesService().getRootNode().node(ConfigurationScope.SCOPE).node("net.jonbuck.tassoo");
		eventManager.addListener(AttachmentTypesUpdatedEvent.class, this);
		
		createControl(this);
	}

	/**
	 * <p>
	 * <b>Populates the attachments table based on the currently selected
	 * task.</b>
	 * </p>
	 */
	private void populateAttachmentsTable() {
		attachmentsTableViewer.setInput(task.getAttachmentContainer());
	}

	/**
	 * 
	 */
	public void processAttachmentTypesUpdatedEvent() {
		if (attachmentsTableViewer != null) {
			attachmentsTableViewer.refresh();
		}
	}

	/**
	 * 
	 */
	public void setInput(Task task) {
		this.task = task;
		populateAttachmentsTable();
	}
}
